package com.xmb.common.service.impl;

import com.github.binarywang.wxpay.bean.request.WxPaySendRedpackRequest;
import com.github.binarywang.wxpay.bean.result.WxPaySendRedpackResult;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
import com.xmb.common.constants.ExchangeTypeEnum;
import com.xmb.common.constants.RedPackStatusEnum;
import com.xmb.common.constants.RewardTypeEnum;
import com.xmb.common.constants.WithdrawStatusEnum;
import com.xmb.common.dao.XmbUserWithdrawDepositBlotterMapper;
import com.xmb.common.dao.XmbUserWithdrawDepositConfigMapper;
import com.xmb.common.dao.XmbUserWithdrawSettingMapper;
import com.xmb.common.dao.XmbWechatRedPackOrderMapper;
import com.xmb.common.entity.*;
import com.xmb.common.exception.BusinessException;
import com.xmb.common.interceptor.CurrentContextUser;
import com.xmb.common.log.LoggerInterface;
import com.xmb.common.params.WxWithdrawParam;
import com.xmb.common.service.*;
import com.xmb.common.utils.NumberUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * Created by lyndon on 2019/5/8.
 */
@Service
public class WithdrawServiceImpl implements WithdrawService,LoggerInterface{

    @Resource
    private XcxAccountService accountService;

    private WxPayService wxPayService;


    @Resource
    private XmbUserWithdrawDepositConfigMapper withdrawDepositConfigMapper;

    @Resource
    private XmbUserWithdrawDepositBlotterMapper withdrawDepositBlotterMapper;

    @Resource
    private XmbUserWithdrawSettingMapper withdrawSettingMapper;


    @Resource
    private XmbWechatRedPackOrderMapper wechatRedPackOrderMapper;


    @Resource
    private UserBalanceService balanceService;

    @Resource
    private UserScoreService scoreService;

    @Resource
    private UserBonusService bonusService;

    @Override
    public void withdrawDeposit(WxWithdrawParam wxWithdrawParam) {
        // 获取提现的配置信息
        XmbUserWithdrawDepositConfig withdrawDepositConfig
                = withdrawDepositConfigMapper.findXmbUserWithdrawDepositConfigBalanceType(wxWithdrawParam.getBalanceType());
        if (withdrawDepositConfig == null) {
            throw new BusinessException("无法获取提现的配置信息");
        }

        // 检查提现金额
        this.checkWithdrawDepositBlotter(wxWithdrawParam,withdrawDepositConfig);

        // 检查是否需要拆单
        List<WxWithdrawParam> withdrawAmountSplitList = this.getWithdrawAmountSplit(wxWithdrawParam,
                withdrawDepositConfig.getConvertRatio());
        withdrawAmountSplitList.forEach(wxWithdrawParam1 -> {
            Long uid = CurrentContextUser.get().getUserId();
            String supplierId = CurrentContextUser.get().getSupplierId();
            this.saveBlotter(uid,supplierId,wxWithdrawParam1,withdrawDepositConfig);
        });
    }

    /**
     * 写入流水数据
     * @param uid
     * @param supplierId
     * @param wxWithdrawParam
     * @param withdrawDepositConfig
     * @return
     */
    @Transactional
    private Long saveBlotter(Long uid,
                             String supplierId,
                             WxWithdrawParam wxWithdrawParam,
                             XmbUserWithdrawDepositConfig withdrawDepositConfig){
        // 写入提现申请的流水
        XmbUserWithdrawDepositBlotter userWithdrawDepositBlotter = new XmbUserWithdrawDepositBlotter();
        userWithdrawDepositBlotter.setUserId(CurrentContextUser.get().getUserId());
        userWithdrawDepositBlotter.setSupplierId(supplierId);
        userWithdrawDepositBlotter.setWithdrawAmount(wxWithdrawParam.getWithdrawAmount());
        userWithdrawDepositBlotter.setConvertRatio(withdrawDepositConfig.getConvertRatio());
        userWithdrawDepositBlotter.setMoneyAmount(NumberUtils.getRoundDownBonusNumber(
                wxWithdrawParam.getWithdrawAmount().multiply(withdrawDepositConfig.getConvertRatio())));
        userWithdrawDepositBlotter.setBalanceType(withdrawDepositConfig.getBalanceType());
        withdrawDepositBlotterMapper.insertSelective(userWithdrawDepositBlotter);


        // 写入红包订单信息
        XmbWechatRedPackOrder wechatRedPackOrder = new XmbWechatRedPackOrder();
        wechatRedPackOrder.setUserId(uid);
        wechatRedPackOrder.setSupplierId(supplierId);
        wechatRedPackOrder.setWxappid(CurrentContextUser.get().getAppid());
        wechatRedPackOrder.setReOpenid(CurrentContextUser.get().getOpenid());
        wechatRedPackOrder.setSendName(withdrawDepositConfig.getRedPackSendName());
        wechatRedPackOrder.setTotalAmount(userWithdrawDepositBlotter.getMoneyAmount());
        wechatRedPackOrder.setWishing(withdrawDepositConfig.getRedPackWishing());
        wechatRedPackOrder.setActName(withdrawDepositConfig.getRedPackActName());
        wechatRedPackOrder.setRemark(withdrawDepositConfig.getRedPackRemark());
        wechatRedPackOrder.setSceneId(withdrawDepositConfig.getRedPackSceneId());
        wechatRedPackOrder.setAmtType(withdrawDepositConfig.getRedPackAmtType());
        wechatRedPackOrder.setOutSenceId(userWithdrawDepositBlotter.getId());
        wechatRedPackOrderMapper.insertSelective(wechatRedPackOrder);

        Long redPackId = wechatRedPackOrder.getId();

        // 写入资产流水
        if (RewardTypeEnum.bonus.name().equals(userWithdrawDepositBlotter.getBalanceType())) {
            XmbUserBonusBlotter userBonusBlotter  = new XmbUserBonusBlotter();
            userBonusBlotter.setUserId(uid);
            userBonusBlotter.setExchangeAmount(userWithdrawDepositBlotter.getWithdrawAmount()
                    .multiply(new BigDecimal(-1)));
            userBonusBlotter.setTotalAmount(scoreService.getTotalScore(uid)
                    .add(userBonusBlotter.getExchangeAmount()));
            userBonusBlotter.setExchangeTitle("提现");
            userBonusBlotter.setExchangeRemark("红包提现");
            userBonusBlotter.setTypeId(userWithdrawDepositBlotter.getRedPackId());
            userBonusBlotter.setSupplierId(supplierId);
            userBonusBlotter.setExchangeType(ExchangeTypeEnum.with_draw.name());
            bonusService.saveBonusBlotter(userBonusBlotter);
        } else if (RewardTypeEnum.balance.name().equals(userWithdrawDepositBlotter.getBalanceType())) {
            XmbUserBalanceBlotter userBalanceBlotter  = new XmbUserBalanceBlotter();
            userBalanceBlotter.setUserId(uid);
            userBalanceBlotter.setExchangeAmount(userWithdrawDepositBlotter.getWithdrawAmount()
                    .multiply(new BigDecimal(-1)));
            userBalanceBlotter.setTotalAmount(scoreService.getTotalScore(uid)
                    .add(userBalanceBlotter.getExchangeAmount()));
            userBalanceBlotter.setExchangeTitle("提现");
            userBalanceBlotter.setExchangeRemark("红包提现");
            userBalanceBlotter.setTypeId(userWithdrawDepositBlotter.getRedPackId());
            userBalanceBlotter.setSupplierId(supplierId);
            userBalanceBlotter.setExchangeType(ExchangeTypeEnum.with_draw.name());
            balanceService.saveBalanceBlotter(userBalanceBlotter);
        } else if (RewardTypeEnum.score.name().equals(userWithdrawDepositBlotter.getBalanceType())) {
            XmbUserScoreBlotter userScoreBlotter  = new XmbUserScoreBlotter();
            userScoreBlotter.setUserId(uid);
            userScoreBlotter.setExchangeAmount(userWithdrawDepositBlotter.getWithdrawAmount()
                    .multiply(new BigDecimal(-1)));
            userScoreBlotter.setTotalAmount(scoreService.getTotalScore(uid)
                    .add(userScoreBlotter.getExchangeAmount()));
            userScoreBlotter.setExchangeTitle("提现");
            userScoreBlotter.setExchangeRemark("红包提现");
            userScoreBlotter.setTypeId(userWithdrawDepositBlotter.getRedPackId());
            userScoreBlotter.setSupplierId(supplierId);
            userScoreBlotter.setExchangeType(ExchangeTypeEnum.with_draw.name());
            scoreService.saveScoreBlotter(userScoreBlotter);
        }
        return redPackId;
    }

    @Override
    public XmbUserWithdrawDepositBlotter confirmWithDraw(XmbUserWithdrawDepositBlotter params)   {

        XmbWechatRedPackOrder sendRedpackParam = wechatRedPackOrderMapper.selectXmbWechatRedPackOrderByOutSenceId(params.getId());

        if (sendRedpackParam == null) {
            throw new BusinessException("无法获取需要发送红包的订单信息");
        }
        XmbUserWithdrawDepositBlotter blotter = withdrawDepositBlotterMapper.selectByPrimaryKey(params.getId());
        if (blotter == null) {
            throw new BusinessException("当前流水不存在");
        }

        if (!WithdrawStatusEnum.wait_apply.name().equals(blotter.getWithdrawStatus())){
            throw new BusinessException("不是待审状态无法审核");
        }

        XmbXcxAccountConfig xcxAccountConfig = accountService.selectByAppid(sendRedpackParam.getWxappid());
        if (xcxAccountConfig == null) {
            throw new BusinessException("无法获取当前小程序的支付配置信息");
        }

        this.setWxPaySendRedpackConfig(xcxAccountConfig);
        WxPaySendRedpackRequest sendRedpackRequest = new WxPaySendRedpackRequest();
        sendRedpackRequest.setWxAppid(xcxAccountConfig.getMainAppid());
        sendRedpackRequest.setReOpenid(sendRedpackParam.getReOpenid());
        sendRedpackRequest.setSendName(sendRedpackParam.getSendName());
        sendRedpackRequest.setTotalAmount(NumberUtils.yuanToFee(sendRedpackParam.getTotalAmount().floatValue()));
        sendRedpackRequest.setTotalNum(sendRedpackParam.getTotalNum());
        sendRedpackRequest.setWishing(sendRedpackParam.getWishing());
        sendRedpackRequest.setActName(sendRedpackParam.getActName());
        sendRedpackRequest.setRemark(sendRedpackParam.getRemark());
        sendRedpackRequest.setSceneId(sendRedpackParam.getSceneId());
        sendRedpackRequest.setRiskInfo(sendRedpackParam.getRiskInfo());
        sendRedpackRequest.setAmtType(sendRedpackParam.getAmtType());
        sendRedpackRequest.setMchBillNo(this.getOutRedPackegNo(blotter));
        sendRedpackRequest.setMsgAppid(xcxAccountConfig.getAppid());
        sendRedpackRequest.setClientIp("192.168.0.1");
        WxPaySendRedpackResult sendRedpackResult = null;
        try {
            sendRedpackResult = wxPayService.sendRedpack(sendRedpackRequest);
            if ("SUCCESS".equals(sendRedpackResult.getResultCode())) {
                // 修改发红包记录
                sendRedpackParam.setWxTransactionNo(sendRedpackResult.getSendListid());
                sendRedpackParam.setMerchantId(sendRedpackResult.getMchId());
                sendRedpackParam.setMerchantTransactionNo(sendRedpackResult.getMchBillno());
                sendRedpackParam.setRedPackStatus(RedPackStatusEnum.send.name());
                wechatRedPackOrderMapper.updateByPrimaryKeySelective(sendRedpackParam);

                // 修改流水
                blotter.setWithdrawStatus(WithdrawStatusEnum.withdrawed.name());
                blotter.setRedPackId(sendRedpackParam.getId());
                blotter.setAuditor(params.getAuditor());
                blotter.setRemarks(params.getRemarks());
                blotter.setAuditTime(LocalDateTime.now());
                withdrawDepositBlotterMapper.updateByPrimaryKeySelective(blotter);
            }
        } catch (WxPayException e) {
            blotter.setFailReason(e.getErrCodeDes());
            blotter.setRedPackId(sendRedpackParam.getId());
            blotter.setAuditor(params.getAuditor());
            blotter.setRemarks(params.getRemarks());
            blotter.setAuditTime(LocalDateTime.now());
            withdrawDepositBlotterMapper.updateByPrimaryKeySelective(blotter);
            logger.info("发送失败,错误编号为:{},错误信息为:{}",e.getErrCode(),e.getErrCodeDes());

        }
        return blotter;
    }

    @Override
    @Transactional
    public XmbUserWithdrawDepositBlotter refuseWithDraw(XmbUserWithdrawDepositBlotter params) {
        XmbUserWithdrawDepositBlotter blotter = withdrawDepositBlotterMapper.selectByPrimaryKey(params.getId());
        if (blotter == null) {
            throw new BusinessException("当前流水不存在");
        }
        if (!WithdrawStatusEnum.wait_apply.name().equals(blotter.getWithdrawStatus())){
            throw new BusinessException("不是待审状态无法操作");
        }

        // 修改流水状态
        blotter.setWithdrawStatus(WithdrawStatusEnum.refused.name());
        blotter.setAuditor(params.getAuditor());
        blotter.setRemarks(params.getRemarks());
        blotter.setAuditTime(LocalDateTime.now());
        withdrawDepositBlotterMapper.updateByPrimaryKeySelective(blotter);

        // 返回资产
        Long uid = blotter.getUserId();
        String supplierId = blotter.getSupplierId();

        // 写入流水
        if (RewardTypeEnum.bonus.name().equals(blotter.getBalanceType())) {
            XmbUserBonusBlotter userBonusBlotter  = new XmbUserBonusBlotter();
            userBonusBlotter.setUserId(uid);
            userBonusBlotter.setExchangeAmount(blotter.getWithdrawAmount());
            userBonusBlotter.setTotalAmount(scoreService.getTotalScore(uid)
                    .add(userBonusBlotter.getExchangeAmount()));
            userBonusBlotter.setExchangeTitle("提现退审返还");
            userBonusBlotter.setExchangeRemark("提现退审返还");
            userBonusBlotter.setTypeId(blotter.getRedPackId());
            userBonusBlotter.setSupplierId(supplierId);
            userBonusBlotter.setExchangeType(ExchangeTypeEnum.back.name());
            bonusService.saveBonusBlotter(userBonusBlotter);
        } else if (RewardTypeEnum.balance.name().equals(blotter.getBalanceType())) {
            XmbUserBalanceBlotter userBalanceBlotter  = new XmbUserBalanceBlotter();
            userBalanceBlotter.setUserId(uid);
            userBalanceBlotter.setExchangeAmount(blotter.getWithdrawAmount());
            userBalanceBlotter.setTotalAmount(scoreService.getTotalScore(uid)
                    .add(userBalanceBlotter.getExchangeAmount()));
            userBalanceBlotter.setExchangeTitle("提现退审返还");
            userBalanceBlotter.setExchangeRemark("提现退审返还");
            userBalanceBlotter.setTypeId(blotter.getRedPackId());
            userBalanceBlotter.setSupplierId(supplierId);
            userBalanceBlotter.setExchangeType(ExchangeTypeEnum.back.name());
            balanceService.saveBalanceBlotter(userBalanceBlotter);
        } else if (RewardTypeEnum.score.name().equals(blotter.getBalanceType())) {
            XmbUserScoreBlotter userScoreBlotter  = new XmbUserScoreBlotter();
            userScoreBlotter.setUserId(uid);
            userScoreBlotter.setExchangeAmount(blotter.getWithdrawAmount());
            userScoreBlotter.setTotalAmount(scoreService.getTotalScore(uid)
                    .add(userScoreBlotter.getExchangeAmount()));
            userScoreBlotter.setExchangeTitle("提现退审返还");
            userScoreBlotter.setExchangeRemark("提现退审返还");
            userScoreBlotter.setTypeId(blotter.getRedPackId());
            userScoreBlotter.setSupplierId(supplierId);
            userScoreBlotter.setExchangeType(ExchangeTypeEnum.back.name());
            scoreService.saveScoreBlotter(userScoreBlotter);
        }
        return blotter;

    }

    /**
     * 检查提现的资产是否足够
     * @param wxWithdrawParam
     */
    private void checkWithdrawDepositBlotter(WxWithdrawParam wxWithdrawParam,
                                             XmbUserWithdrawDepositConfig withdrawDepositConfig){
        Long uid = CurrentContextUser.get().getUserId();
        String supplierId = CurrentContextUser.get().getSupplierId();
        // 检查当日总额是否超限
        BigDecimal defualt = withdrawSettingMapper.selectBySupplierId(supplierId) != null ? withdrawSettingMapper.selectBySupplierId(supplierId).getWithdrawLimit() : new BigDecimal(2000);
        BigDecimal totalAmount = this.getTotalApplyWithdrawAmount(uid);
        if (totalAmount.compareTo(defualt) > 0) {
            throw new BusinessException("当日提现金额已超出可提现范围，请改日再试");
        }

        // 检查资产是否足够
        BigDecimal withDrawMoney = NumberUtils.getRoundDownBonusNumber(
                wxWithdrawParam.getWithdrawAmount().multiply(withdrawDepositConfig.getConvertRatio()));

        if (RewardTypeEnum.bonus.name().equals(wxWithdrawParam.getBalanceType())) {
            BigDecimal totalBonus = bonusService.getTotalBonus(uid);
            if (wxWithdrawParam.getWithdrawAmount().compareTo(totalBonus) > 0 ) {
                throw new BusinessException("奖金不足");
            }
        } else if (RewardTypeEnum.balance.name().equals(wxWithdrawParam.getBalanceType())) {
            BigDecimal totalBalance = balanceService.getTotalBalance(uid);
            if (wxWithdrawParam.getWithdrawAmount().compareTo(totalBalance) > 0 ) {
                throw new BusinessException("余额不足");
            }
        } else if (RewardTypeEnum.score.name().equals(wxWithdrawParam.getBalanceType())) {
            BigDecimal totalScore = scoreService.getTotalScore(uid);
            if (wxWithdrawParam.getWithdrawAmount().compareTo(totalScore) > 0 ) {
                throw new BusinessException("积分不足");
            }
        }
        if (withDrawMoney.compareTo(new BigDecimal(1)) < 0) {
            throw new BusinessException("单次提现金额必须默认大于1元");
        }
    }

    /**
     * 获取用户的提现总金额
     * @param uid
     * @return
     */
    private BigDecimal getTotalApplyWithdrawAmount(Long uid) {
        return Optional.ofNullable(withdrawDepositBlotterMapper.getTotalApplyWithdrawAmount(uid)).orElse(BigDecimal.ZERO);
    }


    /**
     * 对提现货币进行拆单
     * @param wxWithdrawParam
     * @param convertRatio
     * @return
     */
    private List<WxWithdrawParam> getWithdrawAmountSplit(WxWithdrawParam wxWithdrawParam,BigDecimal convertRatio){
        List<WxWithdrawParam> withdrawAmountSplitList = new ArrayList<>();
        // 检查是否需要拆单
        BigDecimal withDrawMoney = NumberUtils.getRoundDownBonusNumber(
                wxWithdrawParam.getWithdrawAmount().multiply(convertRatio));
        if (withDrawMoney.compareTo(new BigDecimal(200)) >=0) {
            // 超过200，自动对积分拆单
            BigDecimal split = BigDecimal.valueOf(200).divide(convertRatio, RoundingMode.DOWN);
            BigDecimal[] results = wxWithdrawParam.getWithdrawAmount().divideAndRemainder(split);
            long times = results[0].setScale(0, BigDecimal.ROUND_DOWN).longValue(); // 向下取整;
            BigDecimal remainder = results[1];
            for (int i=0;i<times;i++) {
                WxWithdrawParam w = new WxWithdrawParam();
                w.setWithdrawAmount(split);
                w.setBalanceType(wxWithdrawParam.getBalanceType());
                withdrawAmountSplitList.add(w);
            }
            if (remainder.multiply(convertRatio).compareTo(new BigDecimal(1)) > 0) {
                WxWithdrawParam w = new WxWithdrawParam();
                w.setWithdrawAmount(remainder);
                w.setBalanceType(wxWithdrawParam.getBalanceType());
                withdrawAmountSplitList.add(w);
            }
        } else {
            withdrawAmountSplitList.add(wxWithdrawParam);
        }
        return withdrawAmountSplitList;
    }

    /**
     * 设置企业发红包配置信息
     */
    private void setWxPaySendRedpackConfig(XmbXcxAccountConfig xcxAccountConfig ){
        wxPayService = new WxPayServiceImpl();
        WxPayConfig payConfig = new WxPayConfig();
        payConfig.setAppId(xcxAccountConfig.getMainAppid());
        payConfig.setMchId(xcxAccountConfig.getMainMchId());
        payConfig.setMchKey(xcxAccountConfig.getMainMchKey());
        payConfig.setKeyPath(xcxAccountConfig.getKeyPath());
        if (xcxAccountConfig.getIsSubAccount()) {
            payConfig.setSubAppId(xcxAccountConfig.getSubAppid());
            payConfig.setSubMchId(xcxAccountConfig.getSubMchId());
        }
        wxPayService.setConfig(payConfig);
    }

    /**
     * 生成商户红包
     * @return
     */
    private String getOutRedPackegNo(XmbUserWithdrawDepositBlotter blotter) {
        String str = "RED"
                + LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"))
                + String.valueOf(blotter.getUserId()).substring(0,4);
        return str.toUpperCase();
    }
}
